package com.base.game.terrain;

import com.base.engine.graphics.Vertex3f;
import com.base.engine.gui.MADUserInterface;
import com.base.engine.system.EDirection;
import com.base.engine.system.ID_2D;
import com.base.engine.system.ID_3D;

import static org.lwjgl.opengl.GL11.*;

public class Terrain {

	/* !!!!  Not more than 48 x 48 chunks = 2304 !!! */ 
	
	static final int SIZE_X = 16,  SIZE_Z = SIZE_X;

	static float getTerrainSizeXAbs() {
		return SIZE_X * Chunk.SIZE_X * Chunk.VOXEL_SIZE;
	}

	static float getTerrainSizeZAbs() {
		return SIZE_Z * Chunk.SIZE_Z * Chunk.VOXEL_SIZE;
	}

	static float getTerrainSizeYAbs() {
		return Chunk.SIZE_Y * Chunk.VOXEL_SIZE;
	}

	public static Vertex3f getCenter() {
		return new Vertex3f(-SIZE_X * Chunk.SIZE_X * Chunk.VOXEL_SIZE / 2,
				-Chunk.SIZE_Y * Chunk.VOXEL_SIZE , -SIZE_Z * Chunk.SIZE_Z
						* Chunk.VOXEL_SIZE / 2);
	}

	private Vertex3f positionAtUpdate;

	private Heightmap heightmap;
	private Chunk[][] chunks;
	private Chunk[] northernLimit, southernLimit, westernLimit, easternLimit;

	public Chunk getChunkByID(ID_2D id){
		return this.chunks[id.getX() % SIZE_X][id.getZ() % SIZE_Z]; 
	}
	
	public Terrain() {
		this.generateHeightmap();
		this.generateChunks();
		this.positionAtUpdate = getCenter();
	}

	private void generateHeightmap() {
//		this.heightmap = new Heightmap(SIZE_X * Chunk.SIZE_X, SIZE_Z
//				* Chunk.SIZE_Z, 10, false);
		this.heightmap = Heightmap.intersect(SIZE_X  * Chunk.SIZE_X, SIZE_Z  * Chunk.SIZE_Z, 8, 12); 
	}

	private void generateChunks() {
		this.chunks = new Chunk[SIZE_X][SIZE_Z];
		this.northernLimit = new Chunk[SIZE_Z];
		this.southernLimit = new Chunk[SIZE_Z];
		this.westernLimit = new Chunk[SIZE_X];
		this.easternLimit = new Chunk[SIZE_X];
		for (int x = 0; x < SIZE_X; x++)
			for (int z = 0; z < SIZE_Z; z++) {
				final int _x = x, _z = z;
//				Thread t = new Thread() {
//					public void run() {
						chunks[_x][_z] = new Chunk(new ID_2D(_x, _z));
						setLimits(_x, _z);
//					}
//				};
//				t.start();
			}
		for (int x = 0; x < SIZE_X; x++)
			for (int z = 0; z < SIZE_Z; z++) {
				// set neighboured chunks
				chunks[x][z].setNeighbourChunk(EDirection.NORTH, chunks[x==SIZE_X-1?0:x+1][z]); 
				chunks[x][z].setNeighbourChunk(EDirection.SOUTH, chunks[x==0?SIZE_X-1:x-1][z]); 
				chunks[x][z].setNeighbourChunk(EDirection.WEST, chunks[x][z==0?SIZE_Z-1:z-1]); 
				chunks[x][z].setNeighbourChunk(EDirection.EAST, chunks[x][z==SIZE_Z-1?0:z+1]); 
				chunks[x][z].applyHeightValues(heightmap.cutSection(
						x * Chunk.SIZE_X, z * Chunk.SIZE_Z, x
								* Chunk.SIZE_X + Chunk.SIZE_X, z
								* Chunk.SIZE_Z + Chunk.SIZE_Z));
			}
	}
	
	private void setLimits(int x, int z) {
		if (x == 0)
			this.southernLimit[z] = this.chunks[x][z];
		else if (x == SIZE_X - 1)
			this.northernLimit[z] = this.chunks[x][z];
		if (z == 0)
			this.westernLimit[x] = this.chunks[x][z];
		else if (z == SIZE_Z - 1)
			this.easternLimit[x] = this.chunks[x][z];
	}

	int getNorthernPosition(){
		return this.northernLimit[0].getID().getX();
	}
	int getSouthernPosition(){
		return this.southernLimit[0].getID().getX();
	}
	int getWesternPosition(){
		return this.westernLimit[0].getID().getZ();
	}
	int getEasternPosition(){
		return this.easternLimit[0].getID().getZ();
	}
	public ID_2D getCenterChunkID(Vertex3f position){
		int x = Math.abs(Math.round(position.getX()) / Chunk.SIZE_X);
		int z = Math.abs(Math.round(position.getZ()) / Chunk.SIZE_Z) ; 
		return new ID_2D(x, z); 
	}
	
	public ID_3D getBlockPositionInChunk(Vertex3f position){
		int x = Math.round(getXPositionInChunk(position)); 
		int y = Math.round(getYPositionInChunk(position)); 
		int z = Math.round(getZPositionInChunk(position)) ; 
		return new ID_3D(x,y,z); 
	}
	
	public Vertex3f getPositionInChunk(Vertex3f position){
		return new Vertex3f(this.getXPositionInChunk(position),
							this.getYPositionInChunk(position),
							this.getZPositionInChunk(position)) ; 
	}

	public float getZPositionInChunk(Vertex3f position) {
		return Math.abs(position.getZ() % Chunk.SIZE_Z);
	}

	public float getYPositionInChunk(Vertex3f position) {
		return Math.abs(position.getY() % Chunk.SIZE_Y) ;
	}

	public float getXPositionInChunk(Vertex3f position) {
		return Math.abs(position.getX() % Chunk.SIZE_X) ;
	}
	
	private void moveSouthernLimitToNorth() {
		int x = this.getSouthernPosition(); 
		float ox = getTerrainSizeXAbs();
		Vertex3f newOffset = new Vertex3f(ox, 0, 0);
		int _x = (x+1) % SIZE_X ; 
		for (int z = 0; z < SIZE_Z; z++) {
			this.northernLimit[z] = this.chunks[x][z];
			this.chunks[x][z].changeOffsetByValue(newOffset);
			this.chunks[x][z].reRender();
			this.southernLimit[z] = this.chunks[_x][z];
		}
		MADUserInterface.getDebuggingScreen().getInfo().set(5, "Northern Chunk Limit: "+x); 
		MADUserInterface.getDebuggingScreen().getInfo().set(6, "Southern Chunk Limit: "+_x); 
	}

	private void moveNorthernLimitToSouth() {
		int x = this.getNorthernPosition(); 
		float ox = getTerrainSizeXAbs();
		Vertex3f newOffset = new Vertex3f(-ox, 0, 0);
		int _x = (x <= 0) ? SIZE_X - 1 : x - 1;
		for (int z = 0; z < SIZE_Z; z++) {
			this.southernLimit[z] = this.chunks[x][z];
			this.chunks[x][z].changeOffsetByValue(newOffset);
			this.chunks[x][z].reRender();
			this.northernLimit[z] = this.chunks[_x][z];
		}
		MADUserInterface.getDebuggingScreen().getInfo().set(5, "Northern Chunk Limit: "+_x); 
		MADUserInterface.getDebuggingScreen().getInfo().set(6, "Southern Chunk Limit: "+x); 
	}

	private void moveWesternLimitToEast() {
		int z = this.getWesternPosition(); 
		float oz = getTerrainSizeZAbs();
		Vertex3f newOffset = new Vertex3f(0, 0, oz);
		int _z = (z+1) % SIZE_Z ; 
		for (int x = 0; x < SIZE_X; x++) {
			this.easternLimit[x] = this.chunks[x][z];
			this.chunks[x][z].changeOffsetByValue(newOffset);
			this.chunks[x][z].reRender();
			this.westernLimit[x] = this.chunks[x][_z];
		}
		MADUserInterface.getDebuggingScreen().getInfo().set(7, "Western Chunk Limit: "+z); 
		MADUserInterface.getDebuggingScreen().getInfo().set(8, "Eastern Chunk Limit: "+_z); 
	}

	private void moveEasternLimitToWest() {
		int z = this.getEasternPosition(); 
		float oz = getTerrainSizeZAbs();
		Vertex3f newOffset = new Vertex3f(0, 0, -oz);
		int _z = (z <= 0) ? SIZE_Z - 1 : z - 1;
		for (int x = 0; x < SIZE_X; x++) {
			this.westernLimit[x] = this.chunks[x][z];
			this.chunks[x][z].changeOffsetByValue(newOffset);
			this.chunks[x][z].reRender();
			this.easternLimit[x] = this.chunks[x][_z];
		}
		MADUserInterface.getDebuggingScreen().getInfo().set(7, "Western Chunk Limit: "+_z); 
		MADUserInterface.getDebuggingScreen().getInfo().set(8, "Eastern Chunk Limit: "+z); 
	}

	private Vertex3f getPosDiff(Vertex3f newPos) {
		return new Vertex3f(newPos.getX() - positionAtUpdate.getX(),
				newPos.getY() - positionAtUpdate.getY(), newPos.getZ()
						- positionAtUpdate.getZ());
	}

	public void render() {
		glPushMatrix();
//		glEnable(GL_LIGHTING); 
//		glLight(GL_LIGHT0, GL_AMBIENT, MADSystem.asFloatBuffer(new float[]{0.5f, 0.5f, 0.5f, 0.5f})); 
//		glLight(GL_LIGHT0, GL_DIFFUSE, MADSystem.asFloatBuffer(new float[]{0.5f, 0.5f, 0.5f, 0.5f})); 
//		glLight(GL_LIGHT0, GL_POSITION, MADSystem.asFloatBuffer(new float[]{-50, 20, -50, 1.0f})); 
//		glLight(GL_LIGHT0, GL_SPOT_DIRECTION, MADSystem.asFloatBuffer(new float[]{-50, 20, -50, 1.0f})); 
		for (int x = 0; x < SIZE_X; x++)
			for (int z = 0; z < SIZE_Z; z++)
				this.chunks[x][z].render();
		glPopMatrix();
	}

	public void update(Vertex3f position) {

		Vertex3f diff = this.getPosDiff(position);
		boolean diffx1, diffx2, diffz1, diffz2;
		diffx1 = diff.getX() > (Chunk.SIZE_X * Chunk.VOXEL_SIZE);
		diffx2 = diff.getX() < (-Chunk.SIZE_X * Chunk.VOXEL_SIZE);
		diffz1 = diff.getZ() > (Chunk.SIZE_Z * Chunk.VOXEL_SIZE);
		diffz2 = diff.getZ() < (-Chunk.SIZE_Z * Chunk.VOXEL_SIZE);
		if (diffx1)
			this.moveLimits(EDirection.SOUTH);
		else if (diffx2)
			this.moveLimits(EDirection.NORTH);
		if (diffz1)
			this.moveLimits(EDirection.WEST);
		else if (diffz2)
			this.moveLimits(EDirection.EAST);

		if (diffx1 || diffx2)
			this.positionAtUpdate = new Vertex3f(position.getX(),
					this.positionAtUpdate.getY(), this.positionAtUpdate.getZ());
		if (diffz1 || diffz2)
			this.positionAtUpdate = new Vertex3f(this.positionAtUpdate.getX(),
					this.positionAtUpdate.getY(), position.getZ());

	}

	public void moveLimits(EDirection dir) {
		switch (dir) {
		case NORTH:
			this.moveSouthernLimitToNorth();
			break;
		case SOUTH:
			this.moveNorthernLimitToSouth();
			break;
		case WEST:
			this.moveEasternLimitToWest();
			break;
		case EAST:
			this.moveWesternLimitToEast();
			break;
		}
		MADUserInterface.getDebuggingScreen().getInfo().set(9, "Moving: "+dir); 
	}

}
